#include "StaticInstancedModel.h"

//Complete forward declarations
#include "StaticModel.h"

StaticInstancedModel::StaticInstancedModel(ID3D11Device* device, unsigned maxInstanceCount)
	: staticModel(0),
	  instancedBuffer(0),
	  isInit(false),
	  numInstancesPassedFrustumTest(0),
	  maxNumInstances(maxInstanceCount)
{
	//Check maxInstanceCount for problems. (0, 1)
	if (maxInstanceCount == 0)
	{
		OutputDebugStringW(L"StaticInstancedModel::StaticInstancedModel() Error - Inst Count == 0\n");
		while(1){}
	}
	if (maxInstanceCount == 1)
	{
		OutputDebugStringW(L"StaticInstancedModel::StaticInstancedModel() Error - Inst Count == 1.");
		OutputDebugStringW(L"\nPlease us StaticModel\n");
		while(1){}
	}

	//Create the static model in memory.
	staticModel = new StaticModel();

	//Create the instancedBuffer - sized to the maximum number 
	//of instances. We will only be allocating memory here and not initing
	//it. 
	D3D11_BUFFER_DESC bd;
	//Its a (per instance) vertex buffer.
	bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	//CPU Access write
	bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	bd.MiscFlags = 0;
	bd.StructureByteStride = 0;
	//Dynamic usage - IE, we can update the ontents of the buffer dynamically. 
	bd.Usage = D3D11_USAGE_DYNAMIC;
	//Each instance will have an XMFLOAT4X4 representing the world matrix. 
	bd.ByteWidth = sizeof(XMFLOAT4X4) * maxInstanceCount;

	//Create buffer
	HR(device->CreateBuffer(&bd, 0, &instancedBuffer));
}

StaticInstancedModel::~StaticInstancedModel() 
{
	//Delete static model. 
	SafeDelete(staticModel);

	//Clear instance data
	instances.clear();

	//Release instanced VB
	ReleaseCOM(instancedBuffer);
}

void StaticInstancedModel::SetAssimpModelsTessellationData(TessellationData& tessData)
{
	staticModel->SetAssimpModelsTessellationData(tessData);
}

void StaticInstancedModel::SetSubmodelsWithSameMaterial(Material& material)
{
	staticModel->SetSubmodelsWithSameMaterial(material);
}

void StaticInstancedModel::SetSubModelMaterial(unsigned index, Material& material)
{
	staticModel->SetSubModelMaterial(index, material);
}

void StaticInstancedModel::SetSubmodelsWithSameReflectionData(XMFLOAT3 &reflectivity)
{
	staticModel->SetSubmodelsWithSameReflectionData(reflectivity);
}

//
//Functions that interact with StaticModel to load geometry.
//

void StaticInstancedModel::CreateBox(float width, float height, float depth,
		StaticModelBasicMaterial* material,
		ID3D11Device* device)
{
	if (staticModel && !isInit)
		staticModel->CreateBox(width, height, depth, material, device);
	else
	{
		OutputDebugStringW(L"StaticInstancedModel Error - staticModel NULL\n");
		while(1){}
	}

	isInit = true;
}

void StaticInstancedModel::CreateSphere(float radius, unsigned sliceCount, unsigned stackCount,
		StaticModelBasicMaterial* material,
		ID3D11Device* device)
{
	if (staticModel && !isInit)
		staticModel->CreateSphere(radius, sliceCount, stackCount, material, device);
	else
	{
		OutputDebugStringW(L"StaticInstancedModel Error - staticModel NULL\n");
		while(1){}
	}

	isInit = true;
}

void StaticInstancedModel::CreateGeoSphere(float radius, unsigned subDivisions, 
		StaticModelBasicMaterial* material,
		ID3D11Device* device)
{
	if (staticModel && !isInit)
		staticModel->CreateGeoSphere(radius, subDivisions, material, device);
	else
	{
		OutputDebugStringW(L"StaticInstancedModel Error - staticModel NULL\n");
		while(1){}
	}

	isInit = true;
}

void StaticInstancedModel::CreateCollum(float bottomRadius, float topRadius, float height,
		unsigned sliceCount, unsigned stackCount, 
		StaticModelBasicMaterial* material,
		ID3D11Device* device)
{
	if (staticModel && !isInit)
		staticModel->CreateCollum(bottomRadius, topRadius, height, 
			sliceCount, stackCount, material, device);
	else
	{
		OutputDebugStringW(L"StaticInstancedModel Error - staticModel NULL\n");
		while(1){}
	}

	isInit = true;
}

void StaticInstancedModel::CreateGrid(float width, float height, 
		unsigned numVerticiesM, unsigned numVerticiesN, 
		StaticModelBasicMaterial* material,
		ID3D11Device* device)
{
	if (staticModel && !isInit)
		staticModel->CreateGrid(width, height, numVerticiesM, numVerticiesN, 
			material, device);
	else
	{
		OutputDebugStringW(L"StaticInstancedModel Error - staticModel NULL\n");
		while(1){}
	}

	isInit = true;
}

void StaticInstancedModel::CreateModelFromOBJFile(
		ID3D11Device* device, 
		LPCWSTR objFilePath,					     
		std::vector<LPCWSTR> &objMaterialFilePath,	 
		std::string textureFolderPath,		
		bool useDisplacementMapping,	             
		TessellationData* tessData,	             
		XMFLOAT2 texScale)
{
	if (staticModel && !isInit)
		staticModel->CreateModelFromOBJFile(device, objFilePath, objMaterialFilePath, 
			textureFolderPath, useDisplacementMapping, tessData, texScale);
	else
	{
		OutputDebugStringW(L"StaticInstancedModel Error - staticModel NULL\n");
		while(1){}
	}

	isInit = true;
}

void StaticInstancedModel::CreateModelThroughAssimp(ID3D11Device* device, 
		std::string& file, std::string& textureDirectory, 
		bool shouldMakeLeftHanded, bool shouldFlipUV, bool shouldFlipWinding, 
		bool shouldFixInwardFacingNormals, 
		bool loadDisplacementMapIfAvailable)
{
	if (staticModel && !isInit)
		staticModel->CreateModelThroughAssimp(device, file, textureDirectory, 
			shouldMakeLeftHanded, shouldFlipUV, shouldFlipWinding, shouldFixInwardFacingNormals, 
			loadDisplacementMapIfAvailable);
	else
	{
		OutputDebugStringW(L"StaticInstancedModel Error - staticModel NULL\n");
		while(1){}
	}

	isInit = true;
}

//
//Interacting with instanced data
//

bool StaticInstancedModel::CreateInstance(XMFLOAT3 position, XMFLOAT3 rotation,
	XMFLOAT3 scale, bool active)
{
	//If current size if < maxNumInstances - go ahead and create the new instance
	//data.
	if (instances.size() < maxNumInstances)
	{
		//Copy data in to a new structure.
		InstanceData d;
		d.Pos = position;
		d.Rot = rotation;
		d.Scale = scale;
		d.Active = active;

		//Push on to vector
		instances.push_back(d);

		//Return true as it has succeeded. 
		return true;
	}
	else
	{
		OutputDebugStringW(L"StaticInstancedModel - Can not create instance (1) - Too many created\n");
		return false;
	}
}

bool StaticInstancedModel::CreateInstance(InstanceData data)
{
	//If current size if < maxNumInstances - go ahead and create the new instance
	//data.
	if (instances.size() < maxNumInstances)
	{
		//Push on to vector
		instances.push_back(data);

		//Return true as it has succeeded. 
		return true;
	}
	else
	{
		OutputDebugStringW(L"StaticInstancedModel - Can not create instance (2) - Too many created\n");
		return false;
	}
}

bool StaticInstancedModel::UpdateInstance(unsigned instanceID, XMFLOAT3 newPosition, 
		XMFLOAT3 newRotation,
		XMFLOAT3 newScale, bool newActiveState)
{
	//ID > num instances. Print error and return false without updating. 
	if (instanceID+1 > instances.size())
	{
		OutputDebugStringW(L"StaticInstancedModel::UpdateInstance() Error (1) - ID > numInstances created\n");
		return false;
	}
	//Check passed - update
	else
	{
		//Reference to the InstanceData
		InstanceData& d = instances[instanceID];

		//Update
		d.Pos = newPosition;
		d.Rot = newRotation;
		d.Scale = newScale;
		d.Active = newActiveState;
	}

	//Return true indicating an update occured.
	return true;
}

bool StaticInstancedModel::UpdateInstance(unsigned instanceID, InstanceData data)
{
	//ID > num instances. Print error and return false without updating. 
	if (instanceID+1 > instances.size())
	{
		OutputDebugStringW(L"StaticInstancedModel::UpdateInstance() Error (2) - ID > numInstances created\n");
		return false;
	}
	//Check passed - update
	else
	{
		//Reference to the InstanceData
		InstanceData& d = instances[instanceID];

		//Update
		d.Pos = data.Pos;
		d.Rot = data.Rot;
		d.Scale = data.Scale;
		d.Active = data.Active;
	}

	//Return true indicating an update occured.
	return true;
}

void StaticInstancedModel::GetInstanceData(unsigned instanceID, 
		XMFLOAT3 &position, XMFLOAT3 &rotation, XMFLOAT3 &scale, bool &active)
{
	//If iD > number of instances, enter infinite loop and print error. 
	if (instanceID+1 > instances.size())
	{
		OutputDebugStringW(L"StaticInstancedModel::GetInstanceData() Error (1) - ID > numInstances created\n");
		while(1){}
	}	
	//Return data
	else
	{
		//Reference to instance data so we can quickly extract data. 
		InstanceData& d = instances[instanceID];

		position = d.Pos;
		rotation = d.Rot;
		scale = d.Scale;
		active = d.Active;
	}
}

InstanceData* StaticInstancedModel::GetInstanceData(unsigned instanceID)
{
	//Return 0 if index > instance count.
	if (instanceID+1 > instances.size())
	{
		OutputDebugStringW(L"StaticInstanceModel::GetInstanceData() Error (2) - ID > numInstances created\n");
		return 0;
	}
	//Return pointer to instance data structure at index. 
	else
		return &instances[instanceID];
}

InstanceData* StaticInstancedModel::operator[](unsigned index)
{
	//Return 0 if index > instance count.
	if (index+1 > instances.size())
	{
		OutputDebugStringW(L"StaticInstanceModel::operator[] Error - ID > numInstances created\n");
		return 0;
	}
	//Return pointer to instance data structure at index. 
	else
		return &instances[index];
}

void StaticInstancedModel::ChangeMaximumInstances(ID3D11Device* device, unsigned newInstanceLimit)
{
	if (newInstanceLimit > maxNumInstances)
	{
		//Release old instance buffer.
		ReleaseCOM(instancedBuffer);

		//Store new maxInstanceCount. 
		maxNumInstances = newInstanceLimit;

		//Create the instancedBuffer - sized to the maximum number 
		//of instances. We will only be allocating memory here and not initing
		//it. 
		D3D11_BUFFER_DESC bd;
		//Its a (per instance) vertex buffer.
		bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
		//CPU Access write
		bd.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		bd.MiscFlags = 0;
		bd.StructureByteStride = 0;
		//Dynamic usage - IE, we can update the ontents of the buffer dynamically. 
		bd.Usage = D3D11_USAGE_DYNAMIC;
		//Each instance will have an XMFLOAT4X4 representing the world matrix. 
		bd.ByteWidth = sizeof(XMFLOAT4X4) * maxNumInstances;

		//Create buffer
		HR(device->CreateBuffer(&bd, 0, &instancedBuffer));
	}
	else
	{
		OutputDebugStringW(L"StaticInstancedModel Warning - Attempting to reduce max instances ");
		OutputDebugStringW(L"or newInstanceLimit == maxNumInstances and thus this would have had no effect. ");
		OutputDebugStringW(L"Ignoring request.\n");
	}
}

XNA::AxisAlignedBox StaticInstancedModel::GetAABBL()
{
	return staticModel->GetAABBL();
}

XNA::AxisAlignedBox StaticInstancedModel::CalculateWorldSpaceAABB(unsigned instanceID)
{
	//Get instance data
	InstanceData* d = GetInstanceData(instanceID);

	//Calculate world space AABB. 
	XNA::AxisAlignedBox aabbW;

	//Transform from XMFLOAT3 to XMVECTOR
	XMVECTOR r = XMLoadFloat3(&d->Rot);
	XMVECTOR t = XMLoadFloat3(&d->Pos);

	XNA::TransformAxisAlignedBox(&aabbW, &GetAABBL(), 
		d->Scale.x, r, t);

	return aabbW;
}

XNA::OrientedBox StaticInstancedModel::CalculateWorldSpaceOABB(unsigned instanceID)
{
	//Get instance data
	InstanceData* d = GetInstanceData(instanceID);

	XNA::OrientedBox oabbW;

	//Construct world matrix
	XMMATRIX translation = XMMatrixTranslation(GetInstanceData(instanceID)->Pos.x,
		GetInstanceData(instanceID)->Pos.y,GetInstanceData(instanceID)->Pos.z);

	XMMATRIX scaleM = XMMatrixScaling(GetInstanceData(instanceID)->Scale.x, 
		GetInstanceData(instanceID)->Scale.y, GetInstanceData(instanceID)->Scale.z);
				
	XMMATRIX rotX = XMMatrixRotationX(GetInstanceData(instanceID)->Rot.x);
	XMMATRIX rotY = XMMatrixRotationY(GetInstanceData(instanceID)->Rot.y);
	XMMATRIX rotZ = XMMatrixRotationZ(GetInstanceData(instanceID)->Rot.z);

	XMMATRIX rot = rotX * rotY * rotZ;
	XMMATRIX wd = (scaleM * rot) * translation;

	//Decompose the World matrix. 
	XMVECTOR scale;
	XMVECTOR rotQuat;
	XMVECTOR trans;
	XMMatrixDecompose(&scale, &rotQuat, &trans, wd);


	//Transform
	XNA::TransformOrientedBox(&oabbW, &staticModel->GetOABBL(), XMVectorGetX(scale), rotQuat, trans);

	//Return 
	return oabbW;
}